跳到主要内容

Java 使用 WebSocket

配置环境

<!-- scope 用 provided 就行,只是为了编译通过,tomcat 会自带。 -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>

别忘了添加 WebApplication 支持(就是传统的 web 文件夹那些),然后修改下 Maven 的打包配置

<!-- 首先在pom文件里把包改成war包 -->
<packaging>war</packaging>

<!-- maven的web项目默认的webroot是在src\main\webapp。如果在此目录下找不到web.xml就抛出以上的异常。 -->
<!-- 如果需要自定义,则更改以下的编译配置(实际上打包成war是用的插件,所以配置相应插件就好了) -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<!--指定web.xml的位置-->
<webXml>WebContent\WEB-INF\web.xml</webXml>
</configuration>
</plugin>
</plugins>
</build>

最后设置下 Tomcat 项目启动时的根路径,以及设置下编码 在启动项的 VM 设置里加上 -Dfile.encoding=UTF-8

编写服务

这里直接使用注解的方式就行了,没必要使用编程式的方法,如果使用编程式的话就是继承 Endpoint 这个抽象类,然后编写 web.xml 开启这个服务(具体找资料)

这里的 @ServerEndpoint("/websocket") 是映射的链接,tomcat 自动会扫描此注解的 class,无须在 xml 中配置什么。

主要接收消息方法:onOpen(建立会话)、onMessage(接收消息)、onClose(结束会话)。MyWebSocket 这个类是多例的,每个会话会单独创建一个实例,所以 session 对象可以定义为成员变量使用。

上面讲了如何接收消息,那如何发送消息给客户端呢?发送消息则由 RemoteEndpoint 完成,其实例是由 Session 维护,发送消息有两种方式,同步和异步

// 同步消息发送实例
this.session.getBasicRemote().sendText(message);
// 异步消息发送实例
this.session.getAsyncRemote().sendText(message);

多说无凭,下面直接看代码吧

// 这个注解用来指定一个 URI,Tomcat 通过扫描它来创建这个服务
@ServerEndpoint(value = "/websocket")
public class MyWebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
// 使用 synchronized 来保证线程安全
public static synchronized int getOnlineCount() {
return onlineCount;
}

public static synchronized void addOnlineCount() {
MyWebSocketServer.onlineCount++;
}

public static synchronized void subOnlineCount() {
MyWebSocketServer.onlineCount--;
}

//concurrent 包的线程安全 Set,用来存放每个客户端对应的 MyWebSocketServer 对象。若要实现服务端与单一客户端通信的话,可以使用 Map 来存放,其中 Key 可以为用户标识
private static Set<MyWebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();

//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;

public MyWebSocketServer(){
System.out.println("-------------Start a WebSocket Service----------------");
}

/**
* 连接建立后触发的方法
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}


/**
* 连接关闭后触发的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}


/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) throws Exception {
System.out.println("来自客户端的消息:" + message);

//群发消息
for(MyWebSocketServer item: webSocketSet){
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}

/**
* 发生错误时调用
* @param session 可选的参数
* @param error 错误
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}

/**
* 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
* @param message 传入的消息
* @throws IOException 抛出一个 IO 错误
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
}

前端测试代码

// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080/websocket');

// Connection opened
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});

执行写上上面的代码就能做到开启一个页面就开启一个新的 WebSocket 连接,关闭一个页面就自动关闭这个 WebSocket 了